use std::mem;

use argon2::{
    password_hash::{rand_core::OsRng, PasswordHasher, SaltString},
    Argon2, PasswordHash, PasswordVerifier,
};
use chrono::NaiveDateTime;

use crate::AppState;
use chat_core::{error::AppError, ChatUser, CreateUser};
use chat_core::{SigninUser, User};
#[allow(unused_imports)]
#[allow(dead_code)]
#[derive(Debug, sqlx::FromRow)]
struct EmailPass {
    email: String,
    password: String,
}

#[allow(dead_code)]
impl AppState {
    pub async fn delete_user_by_email(&self, email: &str) -> Result<(), sqlx::Error> {
        sqlx::query(r#" DELETE FROM users WHERE email = $1 "#)
            .bind(email)
            .execute(&self.pool)
            .await?;
        Ok(())
    }

    pub async fn verify_user_token(
        &self,
        signin_user: &SigninUser,
    ) -> Result<Option<User>, AppError> {
        let user: Option<User> = sqlx::query_as(
            r#"SELECT
        id,fullname, email, password_hash, created_date, created_name, updated_date, updated_name,ws_id
         FROM users WHERE email = $1"#,
        )
        .bind(&signin_user.email)
        .fetch_optional(&self.pool)
        .await?;
        match user {
            Some(mut user) => {
                let password_hash = mem::take(&mut user.password_hash);
                let verified =
                    verify_password(&signin_user.password, &password_hash.unwrap_or_default())?;
                if verified {
                    Ok(Some(user))
                } else {
                    Ok(None)
                }
            }
            None => Ok(None),
        }
    }
    //  通过邮箱查找用户

    pub async fn find_user_by_email(&self, email: &str) -> sqlx::Result<Option<User>, AppError> {
        let users = sqlx::query_as(
            r#"
            SELECT * FROM users
             WHERE email = $1
            "#,
        )
        .bind(email)
        .fetch_optional(&self.pool)
        .await?;
        Ok(users)
    }
    // create user

    pub(crate) async fn create_user(&self, create_user: &CreateUser) -> Result<User, AppError> {
        let has_password = hash_password(&create_user.password)?;
        // check if email exists
        let user_opt = self.find_user_by_email(&create_user.email).await?;
        if user_opt.is_some() {
            return Err(AppError::EmailAlreadyExists(create_user.email.clone()));
        }
        let user = sqlx::query_as(
          r#"INSERT INTO users (fullname, email, password_hash,ws_id)
           VALUES ($1, $2, $3,$4)
           RETURNING  id,fullname, email, password_hash , created_date, created_name, updated_date, updated_name,ws_id"#
          ).bind(&create_user.fullname)
      .bind( &create_user.email)
      .bind(has_password)
      .bind(create_user.ws_id)
      .fetch_one(&self.pool)
      .await?;
        Ok(user)
    }

    // update user
    pub async fn update_user_ws_id_by_user_id(
        &self,
        user_id: i64,
        ws_id: i64,
        updated_name: String,
    ) -> Result<i64, AppError> {
        sqlx::query(
            r#"UPDATE users SET ws_id=$1, updated_date = $2, updated_name = $3 WHERE id = $4"#,
        )
        .bind(ws_id)
        .bind(NaiveDateTime::default())
        .bind(updated_name)
        .bind(user_id)
        .execute(&self.pool)
        .await?;
        Ok(ws_id)
    }
}

pub(crate) fn hash_password(password: &str) -> Result<String, AppError> {
    let salt = SaltString::generate(&mut OsRng);
    let argon2 = Argon2::default();
    let password_hash = argon2
        .hash_password(password.as_bytes(), &salt)?
        .to_string();
    Ok(password_hash)
}

fn verify_password(password: &str, password_hash: &str) -> anyhow::Result<bool, AppError> {
    let argon2 = Argon2::default();
    let parsed_hash = PasswordHash::new(password_hash)?;
    Ok(argon2
        .verify_password(password.as_bytes(), &parsed_hash)
        .is_ok())
}

#[allow(dead_code)]
impl AppState {
    pub async fn fetch_chat_user_by_ids(&self, ids: &[i64]) -> Result<Vec<ChatUser>, AppError> {
        let users = sqlx::query_as(
            r#"
         SELECT id,fullname,email
         FROM users
         WHERE id = ANY($1)
        "#,
        )
        .bind(ids)
        .fetch_all(&self.pool)
        .await?;
        Ok(users)
    }

    pub async fn fetch_all_chat_users(&self, ws_id: i64) -> Result<Vec<ChatUser>, AppError> {
        let users = sqlx::query_as(
            r#"
         SELECT id,fullname,email
         FROM users
         WHERE ws_id = $1
        "#,
        )
        .bind(ws_id)
        .fetch_all(&self.pool)
        .await?;
        Ok(users)
    }
}

#[cfg(test)]
mod tests {

    use super::*;
    use anyhow::Result;
    use jwt_simple::reexports::rand::{self, Rng};

    #[tokio::test]
    async fn test_hash_password() {
        let password = "123456";
        let password_hash = hash_password(password).unwrap();
        assert!(verify_password(password, &password_hash).unwrap());
    }

    #[tokio::test]
    async fn some_awesom_test() -> Result<()> {
        let state = AppState::new_for_test().await?;
        let mut thread_rng = rand::thread_rng();
        let random_number: u8 = thread_rng.gen();
        let fullname = format!("jinyulinlang{0}", random_number);
        let email = format!("jinyulinlang{0}@qq.com", random_number);
        let password = "123456".to_string();
        let create_user = CreateUser::new(fullname, email.clone(), 0, password.clone());
        let user = state.create_user(&create_user).await?;
        let signin_user = SigninUser::new(user.email.to_string(), password);
        let verify_user = state.verify_user_token(&signin_user).await?;
        assert!(verify_user.is_some());
        state.delete_user_by_email(&email).await?;
        Ok(())
    }
    #[tokio::test]
    async fn test_find_by_email() -> Result<()> {
        let state = AppState::new_for_test().await?;
        let mut rng = rand::thread_rng();
        let random_number: u8 = rng.gen();
        // User::delete_by_email("jinyulinlang@qq.com", &pool).await?;
        let fullname = format!("jinyulinlang{0}", random_number);
        let email = format!("jinyulinlang{0}@qq.com", random_number);
        let password = "123456".to_string();

        let create_user = CreateUser::new(fullname, email.clone(), 0, password.clone());
        // let _user = User::create(&create_user, &pool).await?;
        let user = state.create_user(&create_user).await;
        assert!(user.is_ok());
        sqlx::query(" delete from   users  where email = $1;")
            .bind(&email)
            .execute(&state.pool)
            .await?;
        Ok(())
    }

    #[tokio::test]
    async fn test_fetch_all_chat_users_should_work() -> Result<()> {
        let state = AppState::new_for_test().await?;
        let ws_id = 0;
        let mut rng = rand::thread_rng();
        let random_number: u8 = rng.gen();
        // User::delete_by_email("jinyulinlang@qq.com", &pool).await?;
        let fullname = format!("jinyulinlang{0}", random_number);
        let email = format!("jinyulinlang{0}@qq.com", random_number);
        let password = "123456".to_string();
        let create_user = CreateUser::new(fullname, email.clone(), ws_id, password.clone());
        state.create_user(&create_user).await?;
        let users = state.fetch_all_chat_users(create_user.ws_id).await?;
        println!("user size:{}", users.len());
        assert!(!users.is_empty());
        state.delete_user_by_email(&email).await?;
        Ok(())
    }
}
